/**
 * @author DongPing
 * 2022/1/17 17:28
 *
 * 带缓存功能的数据查询工具
 * 将第一次查询的数据缓存起来，第二次查询的时候返回缓存的数据
 *
 * */

import _ from "lodash";

// 缓存时间 ms
const CACHE_TIME = 1000 * 60 * 10;

type TSendRequest<T, P> = (params: P) => Promise<T>;

export class WithCache<T, P> {

	/**
	 * 查询过的数据
	 * */
	private cacheData: Array<{
		// 这一条缓存数据的id
		id: string,
		// 到期时间，时间戳
		expires: number,
		data: T
	}>;

	/**
	 * 正在查询中的数据 (网络请求发送了，但是没返回)
	 * */
	private pendingData: Array<{
		id: string,
		dataPromise: Promise<T>
	}>;

	constructor(
		// 如果缓存里面找不到数据，就调用这个方法查询数据
		private sendRequest: TSendRequest<T, P>
	) {
		this.cacheData = [];
		this.pendingData = [];
	}

	/**
	 * 在缓存中查找数据
	 */
	private findInCache(id: string): T | null {
		const target = this.cacheData.find((item) => item.id === id);
		if(target) {
			if(target.expires >= Date.now()) {
				return target.data;
			} else {
				// 清除过期的缓存数据
				this.cacheData = this.cacheData.filter((item) => item.id !== id);
			}
		}
		return null;
	}

	/**
	 * 在正在请求中的数据中查询是否有匹配的数据
	 * */
	private findInPendingData(id: string): Promise<T> | null {
		return this.pendingData.find((item) => item.id === id)?.dataPromise || null;
	}


	/**
	 * 从每一次的查询参数里面获取一个id，这个id用来决定任意两次请求的结果是否可以认为是一样的
	 * */
	getIdFromParams(params: P): string {
		if(typeof params === 'string') {
			return params
		} else if(_.isPlainObject(params)) {
			return JSON.stringify(params)
		} else if(null === params) {
			return 'null'
		} else if(undefined === params) {
			return 'undefined'
		} else {
			return _.uniqueId('whatever')
		}

	}

	/**
	 * 查询数据，先在缓存中查找数据，找不到就发送网络请求
	 * @param params 发送网络请求使用的参数
	 * */
	queryData(params: P): Promise<T> {
		const id = this.getIdFromParams(params);
		const oldData = this.findInCache(id);
		if(oldData) {
			return Promise.resolve(oldData);
		}
		const pendingData = this.findInPendingData(id);
		if(pendingData) return pendingData;

		const promise = this.sendRequest(params);
		this.pendingData.push({id, dataPromise: promise});

		return promise.then((data) => {
			this.cacheData.push({
				id,
				expires: Date.now() + CACHE_TIME,
				data
			});
			return data;
		})
			.finally(() => {
				this.pendingData = this.pendingData.filter((item) => item.id !== id);
			})
	}
}

/**
 * 创建一个可以缓存数据的数据查询方法
 * */
export function generateCacheRequestFunction<T, P>(sendRequest: TSendRequest<T, P>) {
	const withCache = new WithCache(sendRequest);
	return withCache.queryData.bind(withCache);
}
